/*(c)http://gitee.com/silis/ViewxJS*/

(function (win) {

	var document = win.document,
		supportCustomTag = document.createElement("div");
		supportCustomTag.innerHTML = "<vx>";
		supportCustomTag = supportCustomTag.innerHTML.length > 4;

	if(supportCustomTag)
		document.createElement("vx"); //使ie5-8识别vx标签

	//把字符串的第一个字母转换成大写
	//var toFirstUpper = function(name){
	//	return name.substr(0,1).toUpperCase() + name.substr(1);
	//}

	/*** Element ***/
	function Element_data(element) {
		if (element.runtimeStyle)
			return element.runtimeStyle.vx || (element.runtimeStyle.vx = { names: [], dataset: {} });
		else
			return element.vx || (element.vx = { names: [], dataset: {} });
	}

	function Element_compile(page, targetElement, replaceElement, template, component) {
		var element = replaceElement || targetElement,
			vxData = Element_data(element),
			parentElement = targetElement.parentNode,
			tagName = targetElement.tagName.toUpperCase(),
			initFuns = [], //当值为null时，表示元素带for属性
			startElement, endElement, forElements = [] //#if #for

		if (replaceElement) {

			//复制属性
			for (var i = 0; i < targetElement.attributes.length; i++) {
				var attr = targetElement.attributes[i];
				replaceElement.setAttribute(attr.name, attr.value);
			}

			//复制内容
			Element_setInnerHTML(replaceElement, template || targetElement.innerHTML);

			//替换vx标签元素
			parentElement.replaceChild(replaceElement, targetElement);

		}

		//组件
		if (component) {
			vxData.pointClass = increaseComponentId++;
			var scope = vxData.data = Object.assign({}, component.data); //此处需要优化成深度复制

			if (component.properties)
				for (var key in component.properties)
					scope[key] = element.getAttribute(key);
		}

		Array.prototype.forEach.call(element.attributes, function (attr) {
			if (attr.specified) { //如果这个属性你在源代码或者在脚本中明确指定的话，它总是返回真。否则它是由文档的DTD默认定义的，将总是返回假。
				var attrPrefix = attr.name.substr(0, 3),
					isViewx = attrPrefix == "vx-",
					isTwoWayBind = attr.name.charAt(3) == "-", //#mvvm
					attrName = isViewx ? attr.name.substr(attrPrefix.length + (isTwoWayBind ? 1 : 0)) : attr.name,
					attrNameItems = attrName.split("-"),
					attrType = 0,
					attrInit = true;

				switch (attrName) {
					case "else":
						attr.value = "{{true}}";
					case "elif":
					case "if":
						vxData.type = attrType = 1;

						if (!vxData.template) {
							element.setAttribute("template", "...");
							vxData.template = element.innerHTML;
						}
						break;
					case "for": //#for循环
						//if (element.parentNode.childNodes.length > 3) throw "for missing contain";

						vxData.type = attrType = 2;
						initFuns = null;
						//Element_clearChild(parentElement);
						//var parentVxData = Element_data(parentElement);

						startElement = Element_create("div", "vx-for-start", parentElement, element, 0);
						var startVxData = Element_data(parentElement);
						endElement = Element_create("div", "vx-for-end", parentElement, element, 1);

						if (!startVxData.template) {
							startElement.setAttribute("template", "...");
							startVxData.template = element;
						}

						parentElement.removeChild(element);
						break;
				}

				//一个属性有一个设置程序
				//一个属性有多个逻辑表达式
				//一个逻辑表达式有一个逻辑编译程序
				//一个逻辑表达式有：多个指针表达式 + 多个字符表达式
				//一个指针表达式 => 一个指针有：一个getClassName + 一个getData + 一个文本 + 一个设置程序
				var attrFunc = function () {
					//执行属性表达式，获取属性值
					var attrValue = attrFuncParts.map(function (attrFuncPart) { return attrFuncPart(); });
					if (attrValue.length < 2)
						attrValue = attrValue[0];
					else
						attrValue = attrValue.join("");

					var prevElementVxData = Element_data(Element_previousElementSibling(element) || {});

					switch (attrName) {
						case "else":
						case "elif":
							attrValue = !prevElementVxData.ifs && attrValue;
							prevElementVxData._else = attrFunc;
						case "if":
							if (vxData._if != attrValue) {
								vxData._if = attrValue;
								//Element_clearChild(element);
								//if (attrValue) Element_setInnerHTML(element, vxData.template);
								element.style.display = attrValue ? "" : "none";
								vxData.innerHide = !attrValue;
								compileViewx(page);
							}

							if (vxData.ifs != prevElementVxData.ifs || attrValue) {
								vxData.ifs = prevElementVxData.ifs || attrValue;
								if (vxData._else) vxData._else();
							}
							break;
						case "for": //#for循环
							//Element_clearChild(parentElement);
							forElements.forEach(function(forElement){
								parentElement.removeChild(forElement);
							});
							forElements = [];
							vxData[attrName] = attrValue;
							if (attrValue) {
								var forPointClass = toPointClass(toPointExpression(attr.value));
								forPointClass = Element_getScope(element, parentElement, toPointItem0(forPointClass)).pointClass + "-" + forPointClass;

								var documentFragment = document.createDocumentFragment();
								for (var i = 0; i < attrValue.length; i++) {
									var cloneElement = Element_Clone(element), cloneVxData = Element_data(cloneElement);
									cloneElement.removeAttribute(isViewx ? "vx-for" : "for");
									//cloneVxData.forIndex = i;
									//cloneVxData.forItem = attrValue[i];
									cloneVxData.type = 2;
									cloneVxData.pointClass = forPointClass + "-" + i;
									var forItemScope = cloneVxData.data = {},
										forItemName = element.getAttribute("for-item") || "item",
										forIndexName = element.getAttribute("for-index") || "index";
									
									forItemScope[forItemName] = attrValue[i];
									forItemScope[forIndexName] = i;

									documentFragment.appendChild(cloneElement);
									forElements.push(cloneElement);
								}
								parentElement.insertBefore(documentFragment, endElement);
								compileViewx(page);
							};
							break;
						case "inner-html":
							Element_clearChild(element);
							if (!vxData.innerHide) {
								Element_setInnerHTML(element, attrValue);
								compileViewx(page);
							}
							break;
						case "inner-text":
							Element_clearChild(element);
							if (!vxData.innerHide) element.innerText = attrValue;
							break;
						default:
							if (attrNameItems.length <= 1) {
								if (attrName == "class") attrValue = vxData.names.join(" ") + " " + attrValue;

								if (isViewx && isTwoWayBind) { //#mvvm
									if (attrInit) ["keyup", "change"].forEach(function (eventName) {
										Element_addEventListener2(element, eventName, function () {

											var pointClass = toPointClass(toPointExpression(attr.value)),
												scope = Element_getScope(element, parentElement, toPointItem0(pointClass));

											if (scope != page) pointClass = pointClass.substr(pointClass.indexOf("-") + 1);
											pointClass = scope.pointClass + "-" + pointClass;
											pointClass = pointClass.substr("vx-data-".length);
											
											page.setData(pointClass, element[attrName]);

										}, false);
									})

									element[attrName] = attrValue;
								}

								element.setAttribute(attrName, attrValue);
							} else {
								switch (attrNameItems[0]) {
									case "capture":
										switch (attrName) {
											case "catch":
												if (attrInit) Element_addEventListener(page, element, vxData, attrName, attrNameItems, 2, attrValue, false, true);
												break;
											case "bind":
												if (attrInit) Element_addEventListener(page, element, vxData, attrName, attrNameItems, 2, attrValue, true, true);
												break;
										}
										break;
									case "catch":
										if (attrInit) Element_addEventListener(page, element, vxData, attrName, attrNameItems, 1, attrValue, false, false);
										break;
									case "bind":
										if (attrInit) Element_addEventListener(page, element, vxData, attrName, attrNameItems, 1, attrValue, true, false);
										break;
									case "data":
										vxData.dataset[attrNameItems.slice(1).join("-")] = attrValue;
										break;
								}
							}
							break;
					}

					attrInit = false;
				}

				var attrFuncParts = [];
				if (isViewx) {
					(attr.value.match(/(?:\{\{([^\}]*)\}\})|((?:[^{]+|\{))/g) || []).forEach(function (matchItem) {
						if (matchItem.indexOf("{{") >= 0) {
							attrFuncParts.push(Element_compileLogicExpression(page, element, vxData, parentElement, startElement, matchItem, attrFunc, attrType));
						} else {
							attrFuncParts.push(function () {
								return matchItem; //stringExpression
							});
						}
					});
				} else {
					attrFuncParts.push(function () {
						return element.getAttribute(attrName);
					});
				}

				if (initFuns)
					initFuns.push(attrFunc); //初始化一次
				else if (attrType == 2)
					attrFunc(); //初始化一次

			}
		});


		//vx标签
		if (tagName == "VX") {
			if (vxData.innerText == null) vxData.innerText = element.innerText.trim();
			if (vxData.innerText.substr(0, 2) == "{{" && vxData.innerText.substr(vxData.innerText.length - 2) == "}}") {

				var textFunc = function () {
					element.innerText = expression(page, element);
				};
				var expression = Element_compileLogicExpression(page, element, vxData, parentElement, startElement, vxData.innerText, textFunc, 3);

				if (initFuns)
					initFuns.push(textFunc); //初始化一次
			} else throw vxData.innerText;
		}

		if (initFuns) { //表示没有for属性
			for (var i = 0; i < initFuns.length; i++) initFuns[i](); //初始化一次
			removeClass(element, "vx");
		}
	}

	function Element_create(tagName, tagClass, parentElement, targetElement, insertIndex){
		var element = document.createElement(tagName);
		element.style.display = "none";
		if(tagClass) element.setAttribute("class", tagClass);
		if(insertIndex == 0) parentElement.insertBefore(element, targetElement);
		else if(parentElement.lastChild==targetElement) parentElement.appendChild(element);
		else parentElement.insertBefore(element,targetElement.nextSibling);
				
		return element;
	}

	function Element_compileLogicExpression(page, element, vxData, parentElement, startElement, logicExpressionOuter, /*设置程序*/setFun, /*属性类别*/attrType) {
		var logicExpression = logicExpressionOuter.substr(2, logicExpressionOuter.length - 4).replace(/(?:([a-zA-Z][\w\.\[\]0-9]*))|(?:\"[^\"]*\")|(?:\'[^\"]*\')/g, function (otherExpression, pointExpression) {
			switch (pointExpression) {
				case "true":
				case "false":
					return otherExpression;
			}
			if (pointExpression)
			{
				var pointClass = toPointClass(pointExpression),
					pointItems = pointClass.split("-"),
					scope = Element_getScope(element, parentElement, pointItems[0]);

				if (scope != page) pointClass = pointClass.substr(pointClass.indexOf("-") + 1);
				pointClass = Element_getScope(element, parentElement, pointItems[0]).pointClass + "-" + pointClass;

				//如果是for属性，则把class通知加到parentElement
				var targetElement = attrType != 2 ? element : startElement,
					vxData = Element_data(targetElement),
					pointFuncs = vxData[pointClass];

				if (pointFuncs == null) {
					vxData[pointClass] = pointFuncs = [];
					if (vxData.names) vxData.names.push(pointClass);
					
					addClass(targetElement, pointClass);
				};

				pointFuncs.push(setFun);

				return 'fn("' + pointExpression + '")';
			} else return otherExpression;
		});
		logicExpression = win.eval("0||function(fn){ return " + logicExpression + "}");

		return function () {
			return logicExpression(function (pointExpression) {
				
				var pointClass = toPointClass(pointExpression),
					pointItems = pointClass.split("-"),
					data = Element_getScope(element, parentElement, pointItems[0]).data,
					result;

				result = Element_getObjectData(data, pointItems, 0);
				//if (dataFun === undefined)

				if (result != null)
					return result;
				else
					return "";
			});
		}
	}

	//return:[className, page.data, for.data]
	//如果vx-for时，因为element是template元素，没有parentNode。因此需要通过parentElement传进来
	function Element_getScope(element, parentElement, name) {
		if (element) {
			var vxData = Element_data(element);
			if (vxData.cs != viewx.cs) //编译号不相等
			{
				vxData.scopes = {};
				vxData.cs = viewx.cs;
			}
			
			if (element.tagName != "HTML") { //非HTML标签
				if (vxData.data && vxData.data[name] != undefined) {
					return vxData;
				}
				else { //编译号不相等，刷新scopes
					if (vxData.scopes[name] != undefined)
						return vxData.scopes[name];
					else
						return vxData.scopes[name] = Element_getScope(parentElement || element.parentNode, null, name);
				}
			} else return rootPage; //HTML标签
		} else return rootPage;
	}

	function Element_getObjectData(data, pointItems, start) {
		for (var i = start; i < pointItems.length; i++) {
			if (data == null) return null;
			data = data[pointItems[i]];
			if (typeof (data) == "function") data = data();
		}
		return data;
	}

	function Element_Clone(element) {
		var cloneNode = element.tagName ? document.createElement(element.tagName) : element.cloneNode();

		if (element.attributes)
			for (var i = 0; i < element.attributes.length; i++) {
				var attr = element.attributes[i];
				cloneNode.setAttribute(attr.name, attr.value);
			}

		for (var i = 0; i < element.childNodes.length; i++) {
			cloneNode.appendChild(Element_Clone(element.childNodes[i]));
		}

		return cloneNode;
	}

	function hasClass(obj, cls) {
		return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
	}

	function addClass(obj, cls) {
		if (obj.classList) obj.classList.add(cls);
		else if (!hasClass(obj, cls)) obj.className += " " + cls;
	}

	function removeClass(obj, cls) {
		if (obj.classList) obj.classList.remove(cls);
		else if (hasClass(obj, cls)) {
			var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
			obj.className = obj.className.replace(reg, ' ');
		}
	}

	function Element_clearChild(element) {
		while (element.hasChildNodes()) element.removeChild(element.firstChild);
	}

	function Element_addEventListener(page, element, vxData, attrName, attrNameItems, attrNameIndex, attrValue, eventText, bubble, capture) {
		var eventName = attrNameItems.slice(attrNameIndex).join("-"),
			eventFun = page[attrValue];
		//var eventFun = vxData[attrName];
		//if (eventFun) element.removeEventListener(eventName, eventFun, capture);

		
		if (eventFun)
			Element_addEventListener2(element, eventName, function (e) {
				var e2 = e || win.event; //兼容IE
				if (!bubble) e2.stopPropagation ? e2.stopPropagation() : e2.cancelBubble = true;

				var e3 = {
					target: e2.vxTarget || (e2.vxTarget = { dataset: (e2.target || e2.srcElement).dataset }),
					currentTarget: { 
						dataset: Object.assign({}, element.dataset, vxData.dataset)
					}
				};
				eventFun.call(page, e3);
			}, capture);
	}

	function Element_addEventListener2(element, name, fun, capture) {
		if (element.addEventListener) {
			element.addEventListener(name, fun, capture);
		} else {
			element.attachEvent("on" + name, fun, capture);
		}
	}

	function Element_setInnerHTML(element, input){
		if(supportCustomTag || !win.document.parse)
			element.innerHTML = input;
		else
		{
			element.innerHTML = "";
			element.appendChild(win.document.parse(input));
		}
	}

	function Element_previousElementSibling(element) {
		if (element)
			return element.previousElementSibling
			||
			(element.previousSibling && element.previousSibling.nodeType == 1) ? element.previousSibling : Element_previousElementSibling(element.previousSibling)
    }

	/*** viewx ***/
	var viewx = win.vx = function () {
		compileViewx(rootPage);
	}

	function compileViewx(page) {
		if (viewx.ci) return;
		viewx.ci = true; //编译中
		viewx.cs = (viewx.cs || 0) + 1; //编译号

		try {
			for (var key in components) {
				while (true) {
					var element = document.getElementsByTagName(key)[0];
					var component = components[key];
					if (element) Element_compile(page, element, document.createElement("div"), component.template, component);
					else break;
				}
			}

			while (true) {
				var element = document.getElementsByClassName("vx")[0];
				if (element) Element_compile(page, element);
				else break;
			}

			while (true) {
				var element = document.getElementsByTagName("vx")[0];
				if (element) Element_compile(page, element, document.createElement("span"));
				else break;
			}
		} finally {
			viewx.ci = false;
		}
	};

	/*** Page ***/
	function Page(o) {
		var that = this;
		win.Object.assign(
			that,
			//设置页面的默认值
			{
				data: {}, //页面数据
				observers: {}, //监听页面数据值变化
				_observers: {}, //分析observers所得
				pointClass: "vx-data"
			},
			//通过参数值设置页面属性值
			o);

		for (var keysName in o.observers) {
			(function () {
				var keysFun = o.observers[keysName],
					keys = keysName.split(",");

				var keysFun2 = function () {
					var datas = [];
					for (var i = 0; i < keys.length; i++) {
						datas.push(that.data[key]);
					}
					keysFun.apply(that, datas);
				};

				for (var i = 0; i < keys.length; i++) {
					var key = keys[i] = keys[i].trim(); //去空格
					var keyObserver = that._observers[key]; //获取属性的观察器数组
					if (!keyObserver) keyObserver = that._observers[key] = []; //如果观察器数组不存，则创建一个
					keyObserver.push(keysFun2);
				}
			})()
		}
	};

	Page.prototype = {
		setData: Page_setData
	};

	function Page_setData(a0, a1) {
		var that = this;
		switch (arguments.length) {
			case 1:
				for (var key in a0) Page_setSingleData(that, key, a0[key]);
				if (that._observers) {
					var observer = [];
					for (var key in a0) {
						var keyObserver = that._observers[key];
						if (keyObserver)
							for (var i = 0; i < keyObserver.length; i++) observer.push(keyObserver[i]);
					}
					observer.distinct();
					for (var i = 0; i < observer.length; i++) observer[i]();
				}
				break;
			case 2:
				Page_setSingleData(that, a0, a1);
				if (that._observers) {
					var observer = that._observers[a0];
					if (observer) {
						for (var i = 0; i < observer.length; i++) observer[i]();
					}
				}
				break;
		}
	}

	function Page_setSingleData(page, pointExpression, data) {
		var pointClass = toPointClass(pointExpression),
			pointItems = pointClass.split("-"),
			pageData = page.data,
			vxDataKey = "vx-data-" + pointClass,
			elements = document.getElementsByClassName(vxDataKey);

		pointItems.forEach(function (item, index) {
			if (pageData)
				if (index < pointItems.length - 1)
					pageData = pageData[item];
				else
					pageData[item] = data;
		});

		Array.prototype.forEach.call(elements, function (element) {
			var vxData = Element_data(element);
			var vxSetFuncs = vxData[vxDataKey] || [];
			for (var i = 0; i < vxSetFuncs.length; i++) {
				vxSetFuncs[i]();
			}
		})
	};

	function toPointExpression(attrExpression) {
		return attrExpression.substr(2, attrExpression.length - 4);
	}

	function toPointClass(pointExpression) {
		return pointExpression.replace(/\[(\d+)\]/g, function (a, b) { return "-" + b }).replace(/\./g, "-");
	}

	function toPointItem0(pointClass) {
		return pointClass.substr(0, pointClass.indexOf("-"));
	}

	var rootPage, components = {};
	win.Page = function (o) {
		var usingComponents = o.usingComponents;
		if (usingComponents)
			for (var key in usingComponents) {
				document.createElement(key); //使ie5-8识别组件标签
				components[key.toUpperCase()] = win.Component(usingComponents[key]);
			}

		var onShow = o.onShow || function () { }
		var onHide = o.onHide || function () { }
		o.onShow = function () {
			if (o.servicePath != null) win.require("/api/service.js").set(o.servicePath, this)
			onShow.call(this)
		}
		o.onHide = function () {
			if (o.servicePath != null) win.require("/api/service.js").set(o.servicePath, null)
			onHide.call(this)
		}

		if (rootPage) throw "Page created";
		rootPage = new Page(o);
		document.addEventListener("DOMContentLoaded", function () {
			compileViewx(rootPage);
			if (rootPage.onLoad) rootPage.onLoad();
			if (rootPage.onShow) rootPage.onShow();
		})
	}

	win.getCurrentPages = function () {
		return [rootPage];
	}

	var increaseComponentId = 0;
	win.Component = function (component) {
		if (typeof (component) == "string") component = {
			path: component
		};

		return component;
	}

})(window);